#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <time.h>
#include <unistd.h>

#include "ServerProperties.h"
#include "DataHandler.h"
#include "DebugUtils.h"

/* Create the socket. */
int Socket (int family, int type, int flags)	{
	int sockfd;
	
	if ((sockfd = socket(family, type, flags)) < 0) {
		perror("*** ERROR: an error occur while creating a client socket. ***\n");
		exit(1);

	} else
		return sockfd;
}

/* Assign a IP address at socket door created. */
void Bind (int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen){
	
	if (bind(sockfd, serv_addr, addrlen) < 0) {
		perror("*** ERROR: bind error. ***\n");
		exit(1);
	}
	return;
}


/* Marks the socket referred to by 'listenfd' as a passive socket, that is, as a socket that will
 * be used to accept incoming connection requests using accept. */
void Listen (int sockfd, int backlog){

	if (listen(sockfd, backlog) < 0) {
		perror("*** ERROR: an error occur while creating a listener. ***\n");
		exit(1);
	}
	return;
}

/* Get data and remove first connection of stack. */
int Accept(int sockfd, struct sockaddr *cliaddr, socklen_t *socklen){
	int connfd;
	if ((connfd = accept(sockfd, cliaddr, socklen)) < 0) {
		perror("accept");
		exit(1);
	}
	return connfd;
}

/* Creates a new process by duplicating the calling process. */
pid_t Fork(){
	pid_t processID;
	if ((processID = fork()) < 0){
		perror("*** ERROR: an error occur while fork(). ***\n");
		exit(1);
	}
	return processID;
}

/**
 *  Handles the request of client
 *
 *	@return char* response of bytes
 */
char* handleRequest(Request req){
	char* response;
	switch(req.option){
		case 1:
			response = getNumberMoviesList();
			break;
		case 2:
			response = getAllInfoMoviesList();
			break;
		case 3:
			response = getSinopseMovie(req.value);
			break;
		case 4:
			response = getAllInfoMovie(req.value);
			break;
		case 5:
			response = getMediumMovie(req.value);
			break;
		case 6:
			response = setRateMovie(req);
			break;
		default:
			response = NULL;
			break;
 	} 
	// debug
 	printResponse(response);
	return response;
}


/**
 * Send a responses data
 *
 * @return int number of bytes sended or -1 when an error occur
 */
int sendResponse(int socketClient, char* response){
	int totalSize = 0;
	
	if( response != NULL && strlen(response) >0){
		int size = strlen(response);
		
		int numberOfMsgs = ( size + sizeof(int) ) / MAXLINE;
		if(numberOfMsgs != MAXLINE)
			numberOfMsgs++;
		
		char out[MAXLINE];
		sprintf(out, "%d", &numberOfMsgs);
		memcpy(out+sizeof(int), response, MAXLINE-sizeof(int));
		
		int i;
		for(i=0; i<numberOfMsgs;i++){
			memcpy(out, response, size);
		}
		
	}
	
	return totalSize;
}


/* Handle the message received from client. */
char* HandleUDPClient(int socket, int *cliLen, struct sockaddr_in *cliAddr){
	char buffer[REQUEST_SIZE];
	
	// init buffer
	memset(buffer,0x0,REQUEST_SIZE);
	
	// receive message
	*cliLen = sizeof(*cliAddr);
	int n = recvfrom(socket, buffer, REQUEST_SIZE, 0, (struct sockaddr_in *)cliAddr, cliLen);
	
	// print received msg
	//printf("i've received: [%s]\n", buffer);
	
	if(n > 0){
		Request *req = (Request *) translateRequest(buffer);
		char *arr = handleRequest(*req);
		return arr;
	} else {
		perror("*** ERROR: an error occur receive message. ***\n");
		exit(1);
		
	}
}

/* UDP Server. */
int main (int argc, char **argv) {
	int socket;
	struct sockaddr_in servAddr;
	
	// socket creation
	socket = Socket(AF_INET, SOCK_DGRAM, 0);
	
	// bind local server port
	servAddr.sin_family = AF_INET;
	servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servAddr.sin_port = htons(PORT);
	Bind(socket, (struct sockaddr *)&servAddr, sizeof(servAddr));
	
	initDataHandler();
	
	// server infinite loop
	while(1) {

		int cliLen;
		struct sockaddr_in cliAddr;
		
		// handle udp client request
		char *response = HandleUDPClient(socket, &cliLen, &cliAddr);
		
		// send message size
		int size = strlen(response);		
		int bytes = sendto(socket, &size, sizeof(int), 0, (struct sockaddr *)&cliAddr, cliLen);

		// send message data
		bytes = sendto(socket, response, size, 0, (struct sockaddr *)&cliAddr, cliLen);

		//	SendError Handling
		if(bytes == -1){
			perror("*** ERROR: send error. ***\n");
			exit(1);
		}
		
	}
	
	return 0;
}
